/*
  Syntax highlighting with language autodetection.
  http://softwaremaniacs.org/soft/highlight/
*/

var hljs = new function() {

	/* Utility functions */

	function escape(value) {
	    return value.replace(/&/gm, '&amp;').replace(/</gm, '&lt;');
	}

	function langRe(language, value, global) {
	    return RegExp(
			  value,
			  'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '')
			  );
	}

	function findCode(pre) {
	    for (var i = 0; i < pre.childNodes.length; i++) {
		var node = pre.childNodes[i];
		if (node.nodeName == 'CODE')
		    return node;
		if (!(node.nodeType == 3 && node.nodeValue.match(/\s+/)))
		    break;
	    }
	}

	function blockText(block, ignoreNewLines) {
	    var result = '';
	    for (var i = 0; i < block.childNodes.length; i++)
		if (block.childNodes[i].nodeType == 3) {
		    var chunk = block.childNodes[i].nodeValue;
		    if (ignoreNewLines)
			chunk = chunk.replace(/\n/g, '');
		    result += chunk;
		} else if (block.childNodes[i].nodeName == 'BR')
		    result += '\n';
		else
		    result += blockText(block.childNodes[i]);
	    // Thank you, MSIE...
	    if (/MSIE [678]/.test(navigator.userAgent))
		result = result.replace(/\r/g, '\n');
	    return result;
	}

	function blockLanguage(block) {
	    var classes = block.className.split(/\s+/)
		classes = classes.concat(block.parentNode.className.split(/\s+/));
	    for (var i = 0; i < classes.length; i++) {
		var class_ = classes[i].replace(/^language-/, '');
		if (languages[class_] || class_ == 'no-highlight') {
		    return class_;
		}
	    }
	}

	/* Stream merging */

	function nodeStream(node) {
	    var result = [];
	    (function (node, offset) {
		for (var i = 0; i < node.childNodes.length; i++) {
		    if (node.childNodes[i].nodeType == 3)
			offset += node.childNodes[i].nodeValue.length;
		    else if (node.childNodes[i].nodeName == 'BR')
			offset += 1
			else {
			    result.push({
				    event: 'start',
					offset: offset,
					node: node.childNodes[i]
					});
			    offset = arguments.callee(node.childNodes[i], offset)
				result.push({
					event: 'stop',
					offset: offset,
					node: node.childNodes[i]
				    });
			}
		}
		return offset;
	    })(node, 0);
	    return result;
	}

	function mergeStreams(stream1, stream2, value) {
	    var processed = 0;
	    var result = '';
	    var nodeStack = [];

	    function selectStream() {
		if (stream1.length && stream2.length) {
		    if (stream1[0].offset != stream2[0].offset)
			return (stream1[0].offset < stream2[0].offset) ? stream1 : stream2;
		    else {
			/*
			  To avoid starting the stream just before it should stop the order is
			  ensured that stream1 always starts first and closes last:

			  if (event1 == 'start' && event2 == 'start')
			  return stream1;
			  if (event1 == 'start' && event2 == 'stop')
			  return stream2;
			  if (event1 == 'stop' && event2 == 'start')
			  return stream1;
			  if (event1 == 'stop' && event2 == 'stop')
			  return stream2;

			  ... which is collapsed to:
			*/
			return stream2[0].event == 'start' ? stream1 : stream2;
		    }
		} else {
		    return stream1.length ? stream1 : stream2;
		}
	    }

	    function open(node) {
		var result = '<' + node.nodeName.toLowerCase();
		for (var i = 0; i < node.attributes.length; i++) {
		    var attribute = node.attributes[i];
		    result += ' ' + attribute.nodeName.toLowerCase();
		    if (attribute.nodeValue != undefined && attribute.nodeValue != false && attribute.nodeValue != null) {
			result += '="' + escape(attribute.nodeValue) + '"';
		    }
		}
		return result + '>';
	    }

	    while (stream1.length || stream2.length) {
		var current = selectStream().splice(0, 1)[0];
		result += escape(value.substr(processed, current.offset - processed));
		processed = current.offset;
		if ( current.event == 'start') {
		    result += open(current.node);
		    nodeStack.push(current.node);
		} else if (current.event == 'stop') {
		    var i = nodeStack.length;
		    do {
			i--;
			var node = nodeStack[i];
			result += ('</' + node.nodeName.toLowerCase() + '>');
		    } while (node != current.node);
		    nodeStack.splice(i, 1);
		    while (i < nodeStack.length) {
			result += open(nodeStack[i]);
			i++;
		    }
		}
	    }
	    result += value.substr(processed);
	    return result;
	}

	/* Initialization */

	function compileModes() {

	    function compileMode(mode, language, is_default) {
		if (mode.compiled)
		    return;

		if (!is_default) {
		    mode.beginRe = langRe(language, mode.begin ? mode.begin : '\\B|\\b');
		    if (!mode.end && !mode.endsWithParent)
			mode.end = '\\B|\\b'
			    if (mode.end)
				mode.endRe = langRe(language, mode.end);
		}
		if (mode.illegal)
		    mode.illegalRe = langRe(language, mode.illegal);
		if (mode.relevance == undefined)
		    mode.relevance = 1;
		if (mode.keywords)
		    mode.lexemsRe = langRe(language, mode.lexems || hljs.IDENT_RE, true);
		for (var key in mode.keywords) {
		    if (!mode.keywords.hasOwnProperty(key))
			continue;
		    if (mode.keywords[key] instanceof Object)
			mode.keywordGroups = mode.keywords;
		    else
			mode.keywordGroups = {'keyword': mode.keywords};
		    break;
		}
		if (!mode.contains) {
		    mode.contains = [];
		}
		// compiled flag is set before compiling submodes to avoid self-recursion
		// (see lisp where quoted_list contains quoted_list)
		mode.compiled = true;
		for (var i = 0; i < mode.contains.length; i++) {
		    compileMode(mode.contains[i], language, false);
		}
		if (mode.starts) {
		    compileMode(mode.starts, language, false);
		}
	    }

	    for (var i in languages) {
		if (!languages.hasOwnProperty(i))
		    continue;
		compileMode(languages[i].defaultMode, languages[i], true);
	    }
	}

	/*
	  Core highlighting function. Accepts a language name and a string with the
	  code to highlight. Returns an object with the following properties:

	  - relevance (int)
	  - keyword_count (int)
	  - value (an HTML string with highlighting markup)

	*/
	function highlight(language_name, value) {
	    if (!compileModes.called) {
		compileModes();
		compileModes.called = true;
	    }

	    function subMode(lexem, mode) {
		for (var i = 0; i < mode.contains.length; i++) {
		    if (mode.contains[i].beginRe.test(lexem)) {
			return mode.contains[i];
		    }
		}
	    }

	    function endOfMode(mode_index, lexem) {
		if (modes[mode_index].end && modes[mode_index].endRe.test(lexem))
		    return 1;
		if (modes[mode_index].endsWithParent) {
		    var level = endOfMode(mode_index - 1, lexem);
		    return level ? level + 1 : 0;
		}
		return 0;
	    }

	    function isIllegal(lexem, mode) {
		return mode.illegalRe && mode.illegalRe.test(lexem);
	    }

	    function compileTerminators(mode, language) {
		var terminators = [];

		for (var i = 0; i < mode.contains.length; i++) {
		    terminators.push(mode.contains[i].begin);
		}

		var index = modes.length - 1;
		do {
		    if (modes[index].end) {
			terminators.push(modes[index].end);
		    }
		    index--;
		} while (modes[index + 1].endsWithParent);

		if (mode.illegal) {
		    terminators.push(mode.illegal);
		}

		return langRe(language, '(' + terminators.join('|') + ')', true);
	    }

	    function eatModeChunk(value, index) {
		var mode = modes[modes.length - 1];
		if (!mode.terminators) {
		    mode.terminators = compileTerminators(mode, language);
		}
		mode.terminators.lastIndex = index;
		var match = mode.terminators.exec(value);
		if (match)
		    return [value.substr(index, match.index - index), match[0], false];
		else
		    return [value.substr(index), '', true];
	    }

	    function keywordMatch(mode, match) {
		var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0]
		    for (var className in mode.keywordGroups) {
			if (!mode.keywordGroups.hasOwnProperty(className))
			    continue;
			var value = mode.keywordGroups[className].hasOwnProperty(match_str);
			if (value)
			    return [className, value];
		    }
		return false;
	    }

	    function processKeywords(buffer, mode) {
		if (!mode.keywords)
		    return escape(buffer);
		var result = '';
		var last_index = 0;
		mode.lexemsRe.lastIndex = 0;
		var match = mode.lexemsRe.exec(buffer);
		while (match) {
		    result += escape(buffer.substr(last_index, match.index - last_index));
		    var keyword_match = keywordMatch(mode, match);
		    if (keyword_match) {
			keyword_count += keyword_match[1];
			result += '<span class="'+ keyword_match[0] +'">' + escape(match[0]) + '</span>';
		    } else {
			result += escape(match[0]);
		    }
		    last_index = mode.lexemsRe.lastIndex;
		    match = mode.lexemsRe.exec(buffer);
		}
		result += escape(buffer.substr(last_index, buffer.length - last_index));
		return result;
	    }

	    function processBuffer(buffer, mode) {
		if (mode.subLanguage && languages[mode.subLanguage]) {
		    var result = highlight(mode.subLanguage, buffer);
		    keyword_count += result.keyword_count;
		    return result.value;
		} else {
		    return processKeywords(buffer, mode);
		}
	    }

	    function startNewMode(mode, lexem) {
		var markup = mode.className?'<span class="' + mode.className + '">':'';
		if (mode.returnBegin) {
		    result += markup;
		    mode.buffer = '';
		} else if (mode.excludeBegin) {
		    result += escape(lexem) + markup;
		    mode.buffer = '';
		} else {
		    result += markup;
		    mode.buffer = lexem;
		}
		modes.push(mode);
		relevance += mode.relevance;
	    }

	    function processModeInfo(buffer, lexem, end) {
		var current_mode = modes[modes.length - 1];
		if (end) {
		    result += processBuffer(current_mode.buffer + buffer, current_mode);
		    return false;
		}

		var new_mode = subMode(lexem, current_mode);
		if (new_mode) {
		    result += processBuffer(current_mode.buffer + buffer, current_mode);
		    startNewMode(new_mode, lexem);
		    return new_mode.returnBegin;
		}

		var end_level = endOfMode(modes.length - 1, lexem);
		if (end_level) {
		    var markup = current_mode.className?'</span>':'';
		    if (current_mode.returnEnd) {
			result += processBuffer(current_mode.buffer + buffer, current_mode) + markup;
		    } else if (current_mode.excludeEnd) {
			result += processBuffer(current_mode.buffer + buffer, current_mode) + markup + escape(lexem);
		    } else {
			result += processBuffer(current_mode.buffer + buffer + lexem, current_mode) + markup;
		    }
		    while (end_level > 1) {
			markup = modes[modes.length - 2].className?'</span>':'';
			result += markup;
			end_level--;
			modes.length--;
		    }
		    var last_ended_mode = modes[modes.length - 1];
		    modes.length--;
		    modes[modes.length - 1].buffer = '';
		    if (last_ended_mode.starts) {
			startNewMode(last_ended_mode.starts, '');
		    }
		    return current_mode.returnEnd;
		}

		if (isIllegal(lexem, current_mode))
		    throw 'Illegal';
	    }

	    var language = languages[language_name];
	    var modes = [language.defaultMode];
	    var relevance = 0;
	    var keyword_count = 0;
	    var result = '';
	    try {
		var index = 0;
		language.defaultMode.buffer = '';
		do {
		    var mode_info = eatModeChunk(value, index);
		    var return_lexem = processModeInfo(mode_info[0], mode_info[1], mode_info[2]);
		    index += mode_info[0].length;
		    if (!return_lexem) {
			index += mode_info[1].length;
		    }
		} while (!mode_info[2]);
		if(modes.length > 1)
		    throw 'Illegal';
		return {
		    relevance: relevance,
			keyword_count: keyword_count,
			value: result
			}
	    } catch (e) {
		if (e == 'Illegal') {
		    return {
			relevance: 0,
			    keyword_count: 0,
			    value: escape(value)
			    }
		} else {
		    throw e;
		}
	    }
	}

	/*
	  Highlighting with language detection. Accepts a string with the code to
	  highlight. Returns an object with the following properties:

	  - language (detected language)
	  - relevance (int)
	  - keyword_count (int)
	  - value (an HTML string with highlighting markup)
	  - second_best (object with the same structure for second-best heuristically
	  detected language, may be absent)

	*/
	function highlightAuto(text) {
	    var result = {
		keyword_count: 0,
		relevance: 0,
		value: escape(text)
	    };
	    var second_best = result;
	    for (var key in languages) {
		if (!languages.hasOwnProperty(key))
		    continue;
		var current = highlight(key, text);
		current.language = key;
		if (current.keyword_count + current.relevance > second_best.keyword_count + second_best.relevance) {
		    second_best = current;
		}
		if (current.keyword_count + current.relevance > result.keyword_count + result.relevance) {
		    second_best = result;
		    result = current;
		}
	    }
	    if (second_best.language) {
		result.second_best = second_best;
	    }
	    return result;
	}

	/*
	  Post-processing of the highlighted markup:

	  - replace TABs with something more useful
	  - replace real line-breaks with '<br>' for non-pre containers

	*/
	function fixMarkup(value, tabReplace, useBR) {
	    if (tabReplace) {
		value = value.replace(/^((<[^>]+>|\t)+)/gm, function(match, p1, offset, s) {
			return p1.replace(/\t/g, tabReplace);
		    })
		    }
	    if (useBR) {
		value = value.replace(/\n/g, '<br>');
	    }
	    return value;
	}

	/*
	  Applies highlighting to a DOM node containing code. Accepts a DOM node and
	  two optional parameters for fixMarkup.
	*/
	function highlightBlock(block, tabReplace, useBR) {
	    var text = blockText(block, useBR);
	    var language = 'haskell';
	    var result = highlight(language, text);
	    var original = nodeStream(block);
	    if (original.length) {
		var pre = document.createElement('pre');
		pre.innerHTML = result.value;
		result.value = mergeStreams(original, nodeStream(pre), text);
	    }
	    result.value = fixMarkup(result.value, tabReplace, useBR);

	    var class_name = block.className;
	    if (!class_name.match('(\\s|^)(language-)?' + language + '(\\s|$)')) {
		class_name = class_name ? (class_name + ' ' + language) : language;
	    }
	    if (/MSIE [678]/.test(navigator.userAgent) && block.tagName == 'CODE' && block.parentNode.tagName == 'PRE') {
		// This is for backwards compatibility only. IE needs this strange
		// hack becasue it cannot just cleanly replace <code> block contents.
		var pre = block.parentNode;
		var container = document.createElement('div');
		container.innerHTML = '<pre><code>' + result.value + '</code></pre>';
		block = container.firstChild.firstChild;
		container.firstChild.className = pre.className;
		pre.parentNode.replaceChild(container.firstChild, pre);
	    } else {
		block.innerHTML = result.value;
	    }
	    block.className = class_name;
	    block.result = {
		language: language,
		kw: result.keyword_count,
		re: result.relevance
	    };
	    if (result.second_best) {
		block.second_best = {
		    language: result.second_best.language,
		    kw: result.second_best.keyword_count,
		    re: result.second_best.relevance
		};
	    }
	}

	/*
	  Applies highlighting to all <pre><code>..</code></pre> blocks on a page.
	*/
	function initHighlighting() {
	    if (initHighlighting.called)
		return;
	    initHighlighting.called = true;
	    var pres = document.getElementsByTagName('pre');
	    for (var i = 0; i < pres.length; i++) {
		var code = findCode(pres[i]);
		if (code)
		    highlightBlock(code, hljs.tabReplace);
	    }
	}

	/*
	  Attaches highlighting to the page load event.
	*/
	function initHighlightingOnLoad() {
	    if (window.addEventListener) {
		window.addEventListener('DOMContentLoaded', initHighlighting, false);
		window.addEventListener('load', initHighlighting, false);
	    } else if (window.attachEvent)
		window.attachEvent('onload', initHighlighting);
	    else
		window.onload = initHighlighting;
	}

	var languages = {}; // a shortcut to avoid writing "this." everywhere

	/* Interface definition */

	this.LANGUAGES = languages;
	this.highlight = highlight;
	this.highlightAuto = highlightAuto;
	this.fixMarkup = fixMarkup;
	this.highlightBlock = highlightBlock;
	this.initHighlighting = initHighlighting;
	this.initHighlightingOnLoad = initHighlightingOnLoad;

	// Common regexps
	this.IDENT_RE = '[a-zA-Z][a-zA-Z0-9_]*';
	this.UNDERSCORE_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*';
	this.NUMBER_RE = '\\b\\d+(\\.\\d+)?';
	this.C_NUMBER_RE = '\\b(0x[A-Za-z0-9]+|\\d+(\\.\\d+)?)';
	this.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~';

	// Common modes
	this.BACKSLASH_ESCAPE = {
	    begin: '\\\\.', relevance: 0
	};
	this.APOS_STRING_MODE = {
	    className: 'string',
	    begin: '\'', end: '\'',
	    illegal: '\\n',
	    contains: [this.BACKSLASH_ESCAPE],
	    relevance: 0
	};
	this.QUOTE_STRING_MODE = {
	    className: 'string',
	    begin: '"', end: '"',
	    illegal: '\\n',
	    contains: [this.BACKSLASH_ESCAPE],
	    relevance: 0
	};
	this.C_LINE_COMMENT_MODE = {
	    className: 'comment',
	    begin: '//', end: '$'
	};
	this.C_BLOCK_COMMENT_MODE = {
	    className: 'comment',
	    begin: '/\\*', end: '\\*/'
	};
	this.HASH_COMMENT_MODE = {
	    className: 'comment',
	    begin: '#', end: '$'
	};
	this.NUMBER_MODE = {
	    className: 'number',
	    begin: this.NUMBER_RE,
	    relevance: 0
	};
	this.C_NUMBER_MODE = {
	    className: 'number',
	    begin: this.C_NUMBER_RE,
	    relevance: 0
	};

	// Utility functions
	this.inherit = function(parent, obj) {
	    var result = {}
	    for (var key in parent)
		result[key] = parent[key];
	    if (obj)
		for (var key in obj)
		    result[key] = obj[key];
	    return result;
	}
    }();

/*
  Language: Haskell
  Author: Jeremy Hull <sourdrums@gmail.com>
*/

hljs.LANGUAGES.haskell = function(){
    var LABEL = {
	className: 'label',
	begin: '\\b[A-Z][\\w\']*',
	relevance: 0
    };
    var CONTAINER = {
	className: 'container',
	begin: '\\(', end: '\\)',
	contains: [
    {className: 'label', begin: '\\b[A-Z][\\w\\(\\)\\.\']*'},
    {className: 'title', begin: '[_a-z][\\w\']*'}
		   ]
    };

    return {
	defaultMode: {
	    keywords: {
		'keyword': {
		    'let': 1, 'in': 1, 'if': 1, 'then': 1, 'else': 1, 'case': 1, 'of': 1,
			'where': 1, 'do': 1, 'module': 1, 'import': 1, 'hiding': 1,
			'qualified': 1, 'type': 1, 'data': 1, 'newtype': 1, 'deriving': 1,
			'class': 1, 'instance': 1, 'null': 1, 'not': 1, 'as': 1
			}
	    },
		contains: [
			   {
			       className: 'comment',
				   begin: '--', end: '$'
				   },
			   {
			       className: 'comment',
				   begin: '{-', end: '-}'
				   },
			   {
			       className: 'string',
				   begin: '\\s+\'', end: '\'',
				   contains: [hljs.BACKSLASH_ESCAPE],
				   relevance: 0
				   },
			   hljs.QUOTE_STRING_MODE,
			   {
			       className: 'import',
				   begin: '\\bimport', end: '$',
				   keywords: {'import': 1, 'qualified': 1, 'as': 1, 'hiding': 1},
				   contains: [CONTAINER]
				   },
			   {
			       className: 'module',
				   begin: '\\bmodule', end: 'where',
				   keywords: {'module': 1, 'where': 1},
				   contains: [CONTAINER]
				   },
			   {
			       className: 'class',
				   begin: '\\b(class|instance|data|(new)?type)', end: '(where|$)',
				   keywords: {'class': 1, 'where': 1, 'instance': 1,'data': 1,'type': 1,'newtype': 1, 'deriving': 1},
				   contains: [LABEL]
				   },
			   hljs.C_NUMBER_MODE,
			   {
			       className: 'shebang',
				   begin: '#!\\/usr\\/bin\\/env\ runhaskell', end: '$'
				   },
			   LABEL,
			   {
			       className: 'title', begin: '^[_a-z][\\w\']*'
				   }
			   ]
		}
    };
}();
